/*
 *  analex.c
 *
 */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include <assert.h>
#include "SymbolTable.hpp"
#include "ErrorManager.h"
#include "analex.h"

static FILE *file;
static char buffer[BUFFER_SIZE], caracter;
static int lexeme_idx, lexeme_length, currentToken, buffer_index, currentState, line;
static char *lexeme;

/*******************************************************************************************************************
* A tabela de IDs será construída verdadeiramente no analisador semântico, a partir de informações contidas na AST *
*    IDSymbolTable IDTable;                                                                                        *
********************************************************************************************************************/
static LiteralSymbolTable LiteralTable;
static ReservedWordSymbolTable ReservedWordTable;

static char nextChar();
static void concatenateLexeme(char caracter);

/********************************************************************
*               Construtor do analisador lexico                     *
********************************************************************/
void analexConstructor (FILE *fl) {

    file = fl;
    line = 1;
    assert(BUFFER_SIZE > 1);
    buffer[BUFFER_SIZE - 1] = SENTINEL;
    buffer_index = BUFFER_SIZE - 1;
    currentState = 0;
    currentToken = 0;
    lexeme_idx = 0;
    lexeme_length = STD_LEXEME_LEN;
    lexeme = (char*) malloc(lexeme_length * sizeof(char));
    if(!lexeme){
         ErrorMessage(MEMORY_OVERFLOW,0);
         exit(EXIT_FAILURE);
    }
    caracter = nextChar();
    return;
}

/********************************************************************
*               Destrutor do analizador lexico                      *
********************************************************************/
void analexDestructor () {
/***Impressão de tabelas - Primeira etapa do trabalho
    ReservedWordTable.printTable();
    LiteralTable.printTable();
    IDTable.printTable();
*/
    free(lexeme);
    return;
}

/********************************************************************
*               Retorna o proximo caracter do buffer                *
********************************************************************/
static char nextChar () {
    size_t last;
    char Caracter;
    if (buffer[buffer_index] != SENTINEL){
        Caracter = buffer[buffer_index];
        buffer_index++;
        return Caracter;
    }
    //Reloading 'buffer'
    last = fread(buffer, sizeof(char), BUFFER_SIZE - 1, file);
    if (last < (BUFFER_SIZE - 1)) buffer[last] = EOF;
    buffer_index = 0;
    return nextChar();
}

static void concatenateLexeme(char caracter){
    if(lexeme_idx == (lexeme_length - 1)){
        lexeme_length += STD_LEXEME_LEN;
        lexeme = (char*) realloc(lexeme, sizeof(char) * lexeme_length);

        if(lexeme == NULL){
            ErrorMessage(MEMORY_OVERFLOW, 0);
            exit(EXIT_FAILURE);
        }
    }
    lexeme[lexeme_idx] = caracter;
    lexeme_idx++;
    lexeme[lexeme_idx] = SENTINEL;
}

/********************************************************************
*               Retorna o proximo token                             *
********************************************************************/
int nextToken () {
    currentState = 0;
    lexeme_idx = 0;
    int token = 0;
    while ( 1 ) {
        switch ( currentState ) {
            case 0 :
                switch (caracter) {
                    case '.' : concatenateLexeme(caracter); caracter = nextChar(); currentState = 8; break;
                    case ';' : caracter = nextChar(); currentState = 12; break;
                    case '(' : caracter = nextChar(); currentState = 13; break;
                    case ')' : caracter = nextChar(); currentState = 14; break;
                    case '[' : caracter = nextChar(); currentState = 15; break;
                    case ']' : caracter = nextChar(); currentState = 16; break;
                    case '{' : caracter = nextChar(); currentState = 17; break;
                    case '}' : caracter = nextChar(); currentState = 18; break;
                    case '+' : caracter = nextChar(); currentState = 23; break;
                    case '-' : caracter = nextChar(); currentState = 24; break;
                    case '*' : caracter = nextChar(); currentState = 25; break;
                    case '/' : caracter = nextChar(); currentState = 26; break;
                    case '%' : caracter = nextChar(); currentState = 27; break;
                    case '&' : caracter = nextChar(); currentState = 28; break;
                    case '|' : caracter = nextChar(); currentState = 29; break;
                    case '=' : caracter = nextChar(); currentState = 34; break;
                    case '>' : caracter = nextChar(); currentState = 36; break;
                    case '!' : caracter = nextChar(); currentState = 38; break;
                    case '<' : caracter = nextChar(); currentState = 40; break;
                    case ',' : caracter = nextChar(); currentState = 42; break;
                    case '"' : concatenateLexeme(caracter); caracter = nextChar(); currentState = 49; break;
                    case EOF : currentState = 51; break;

                    default :
                        if (isdigit(caracter)){
                            concatenateLexeme(caracter);
                            caracter = nextChar();
                            currentState = 1;
                            break;
                        }
                        if (isalpha(caracter)) {
                            concatenateLexeme(caracter);
                            caracter = nextChar();
                            currentState = 10;
                            break;
                        }
                        if (isspace(caracter)) {
                            if(caracter == '\n')line++;
                            caracter = nextChar();
                            currentState = 0;
                            break;
                        }
                        caracter = nextChar();
                        currentState = 47;
                        break;
                }
                break;

            case 1 : if(isdigit(caracter)) {
                        concatenateLexeme(caracter);
                        caracter = nextChar();
                        currentState = 1;
                        break;
                     }
                     if (caracter=='e' || caracter == 'E') {
                         concatenateLexeme(caracter);
                         caracter = nextChar();
                         currentState = 3;
                         break;
                     }
                     if (caracter == '.' ){
                         concatenateLexeme(caracter);
                         caracter = nextChar();
                         currentState = 4;
                         break;
                     }
                     currentState = 2;
                     break;

            case 2 : return INTNUM;

            case 3 : if(isdigit(caracter)){
                        concatenateLexeme(caracter);
                        caracter = nextChar();
                        currentState = 6;
                        break;
                     }
                     if(caracter == '+' || caracter == '-'){
                        concatenateLexeme(caracter);
                        caracter = nextChar();
                        currentState = 5;
                        break;
                     }
                     currentState = 7;
                     break;

            case 4 : if(isdigit(caracter)){
                        concatenateLexeme(caracter);
                        caracter = nextChar();
                        currentState = 4;
                        break;
                     }
                     if(caracter == 'e' || caracter == 'E'){
                        concatenateLexeme(caracter);
                        caracter = nextChar();
                        currentState = 3;
                        break;
                     }
                     currentState = 7;
                     break;

            case 5 : if(isdigit(caracter)) {
                        concatenateLexeme(caracter);
                        caracter = nextChar();
                        currentState = 6;
                        break;
                     }
                     currentState = 52;
                     break;

            case 6: if(isdigit(caracter)) {
                        concatenateLexeme(caracter);
                        caracter = nextChar();
                        currentState = 6;
                        break;
                     }
                     currentState = 7;
                     break;

            case 7 : return REALNUM;

            case 8 : if(isdigit(caracter)){
                        concatenateLexeme(caracter);
                        caracter = nextChar();
                        currentState = 4;
                        break;
                     }
                     currentState = 9;
                     break;

            case 9 : return DOT;

            case 10 : if (isdigit(caracter) || isalpha(caracter) || caracter == '_' ){
                        concatenateLexeme(caracter);
                        caracter = nextChar();
                        currentState = 10;
                        break;
                      }
                      currentState = 11;
                      break;

            case 11 : token = ReservedWordTable.searchReservedWord(lexeme);
                      if(token == 0) {
                        /*** Tabela de IDs não será mais construída pelo analex **
                        IDTable.addSymbol(lexeme);
                        **********************************************************/
                        return ID;
                      }else return token;

            case 12 : return SEMICOLON;

            case 13 : return LPARENT;

            case 14 : return RPARENT;

            case 15 : return LBRACKET;

            case 16 : return RBRACKET;

            case 17 : return LBRACE;

            case 18 : return RBRACE;

            case 19 : return NOT;

            case 20 : return ASSIGN;

            case 21 : return GREATERT;

            case 22 : return LESST;

            case 23 : return PLUS;

            case 24 : return SUB;

            case 25 : if(caracter == '/'){
                        caracter = nextChar();
                        currentState = 48;
                        break;
                      }
                      currentState = 53;
                      break;

            case 26 : if ( caracter == '*' ) {
                        caracter = nextChar();
                        currentState = 43;
                        break;
                      }
                      currentState = 44;
                      break;

            case 27 : return MOD;

            case 28 : if(caracter == '&'){
                        caracter = nextChar();
                        currentState = 30;
                        break;
                      }
                      currentState = 31;
                      break;

            case 29 : if(caracter == '|'){
                        caracter = nextChar();
                        currentState = 32;
                        break;
                      }
                      currentState = 33;
                      break;

            case 30 : return ANDLOGIC;

            case 31 : return ANDBIT;

            case 32 : return ORLOGIC;

            case 33 : return ORBIT;

            case 34 : if ( caracter == '=' ) {
                        caracter = nextChar();
                        currentState = 35;
                        break;
                      }
                      currentState = 20;
                      break;

            case 35: return EQUAL;

            case 36 : if ( caracter == '=' ) {
                        caracter = nextChar();
                        currentState = 37;
                        break;
                      }
                      currentState = 21;
                      break;

            case 37 : return GREATEREQ;

            case 38 : if ( caracter == '=' ) {
                        caracter = nextChar();
                        currentState = 39;
                        break;
                      }
                      currentState = 19;
                      break;

            case 39 : return NOTEQUAL;

            case 40 : if ( caracter == '=' ) {
                        caracter = nextChar();
                        currentState = 41;
                        break;
                      }
                      currentState = 22;
                      break;

            case 41 : return LESSEQ;

            case 42 : return COMMA;

            case 43 : if ( caracter == '*' ) {
                        caracter = nextChar();
                        currentState = 45;
                        break;
                      }
                      if ( caracter == EOF ) {
                        currentState = 46;
                        break;
                      }
                      if( caracter == '\n') line++;
                      caracter = nextChar();
                      currentState = 43;
                      break;

            case 44 : return DIV;

            case 45 : if ( caracter == '/' ) {
                        caracter = nextChar();
                        currentState = 0;
                        break;
                      }
                      currentState = 43;
                      break;

            case 46 : ErrorMessage(UNEXPECTED_EOF_COMMENTARY, line);
                      return EOF;

            case 47 : ErrorMessage(INVALID_CARACTER, line);
                      return nextToken();

            case 48 : ErrorMessage(UNEXPECTED_COMMENTARY_END, line);
                      return nextToken();

            case 49 : if ( caracter == '"' ) {
                        concatenateLexeme(caracter);
                        caracter = nextChar();
                        currentState = 50;
                        break;
                      }
                      if (caracter == EOF ) {
                          currentState = 54;
                          break;
                      }
                      concatenateLexeme(caracter);
                      caracter = nextChar();
                      currentState = 49;
                      break;

            case 50 : LiteralTable.addSymbol(lexeme);
                      return LITERAL;

            case 51 : return EOF;

            case 52 : ErrorMessage(INVALID_NUMBER, line);
                      return nextToken();

            case 53 : return MULT;

            case 54 : ErrorMessage(UNEXPECTED_EOF_LITERAL, line);
                      return EOF;
        }
    }
}

char* getLexeme(){
    return lexeme;
}

int getLine(){
    return line;
}

int getLexemeLength(){
    return lexeme_length;
}

/********************************************************************
*               Imprime um token                                    *
********************************************************************/
void printToken(int token){
     char *strtoken[] = {(char*)"CLASS", (char*)"ID", (char*)"EXTENDS", (char*)"PUBLIC", (char*)"STATIC", (char*)"VOID",
      (char*)"INT", (char*)"FLOAT", (char*)"BOOLEAN", (char*)"IF", (char*)"ELSE", (char*)"WHILE", (char*)"SYSTEM", (char*)"OUT", (char*)"PRINTLN",
      (char*)"IN", (char*)"READ", (char*)"RETURN", (char*)"NEW", (char*)"SEMICOLON", (char*)"LITERAL", (char*)"DOT", (char*)"LENGTH",
      (char*)"TRUE", (char*)"FALSE", (char*)"THIS", (char*)"NULL", (char*)"LPARENT", (char*)"RPARENT",
      (char*)"LBRACKET", (char*)"RBRACKET", (char*)"LBRACE", (char*)"RBRACE", (char*)"NOT", (char*)"ASSIGN", (char*)"EQUAL",
      (char*)"LESST", (char*)"LESSQ", (char*)"GREATERT", (char*)"GREATEREQ", (char*)"NOTEQUAL", (char*)"PLUS", (char*)"SUB", (char*)"DIV",
      (char*)"MULT", (char*)"MOD", (char*)"ANDBIT", (char*)"ANDLOGIC", (char*)"ORBIT", (char*)"ORLOGIC", (char*)"COMMA", (char*)"REALNUM",
      (char*)"INTNUM", (char*)"BOOLEAN", (char*)"REALVECTOR", (char*)"INTVECTOR"};
      strtoken[-11] = (char*)"EOF";

    if(token == 11 || token == 30) fprintf(stdout, "%s ", strtoken[token - 10]);
    else fprintf(stdout, "%s ", strtoken[token - 10]);

}
